

#include <jni.h>
#include <opencv2/core.hpp>
#include <opencv2/objdetect.hpp>
#include <string>
#include <vector>
#include <string>
#include <opencv2/imgproc/types_c.h>
#include <android/log.h>
#include <opencv2/imgproc.hpp>
#include <opencv2/imgcodecs.hpp>


#define LOG_TAG "face_dector"

#define LOGD(...)  __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG,__VA_ARGS__)
#define LOGE(...)  __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
using namespace std;
using namespace cv;




inline void vector_Rect_to_Mat(vector<Rect> &v_rect, Mat &mat) {
    mat = Mat(v_rect, true);
}

class CascadeDetectorAdapter : public DetectionBasedTracker::IDetector {
public:
    CascadeDetectorAdapter(cv::Ptr<cv::CascadeClassifier> detector) :
            IDetector(),
            Detector(detector) {
        LOGD("CascadeDetectorAdapter::Detect::Detect");
        CV_Assert(detector);
    }

    void detect(const cv::Mat &Image, std::vector<cv::Rect> &objects) {
        LOGD("CascadeDetectorAdapter::Detect: begin");
        LOGD("CascadeDetectorAdapter::Detect: scaleFactor=%.2f, minNeighbours=%d, minObjSize=(%dx%d), maxObjSize=(%dx%d)", scaleFactor, minNeighbours, minObjSize.width, minObjSize.height, maxObjSize.width, maxObjSize.height);
        Detector->detectMultiScale(Image, objects, scaleFactor, minNeighbours, 0, minObjSize, maxObjSize);
        LOGD("CascadeDetectorAdapter::Detect: end");
    }

    virtual ~CascadeDetectorAdapter() {
        LOGD("CascadeDetectorAdapter::Detect::~Detect");
    }

private:
    CascadeDetectorAdapter();

    cv::Ptr<cv::CascadeClassifier> Detector;
};


struct DetectorAgregator {
    cv::Ptr<CascadeDetectorAdapter> mainDetector;
    cv::Ptr<CascadeDetectorAdapter> trackingDetector;

    cv::Ptr<DetectionBasedTracker> tracker;

    DetectorAgregator(cv::Ptr<CascadeDetectorAdapter> &_mainDetector, cv::Ptr<CascadeDetectorAdapter> &_trackingDetector) :
            mainDetector(_mainDetector),
            trackingDetector(_trackingDetector) {
        CV_Assert(_mainDetector);
        CV_Assert(_trackingDetector);

        DetectionBasedTracker::Parameters DetectorParams;
        tracker = makePtr<DetectionBasedTracker>(mainDetector, trackingDetector, DetectorParams);
    }
};

extern "C"
JNIEXPORT jlong JNICALL
Java_com_ooo_ttt_opencvlib_FaceDector_nativeCreateObject(JNIEnv *jenv, jclass clazz, jstring jFileName, jint faceSize) {
    LOGD("face enter");
    const char *jnamestr = jenv->GetStringUTFChars(jFileName, NULL);
    string stdFileName(jnamestr);
    jlong result = 0;

    try {
        cv::Ptr<CascadeDetectorAdapter> mainDetector = makePtr<CascadeDetectorAdapter>(
                makePtr<CascadeClassifier>(stdFileName));

        cv::Ptr<CascadeDetectorAdapter> trackingDetector = makePtr<CascadeDetectorAdapter>(
                makePtr<CascadeClassifier>(stdFileName));

        result = (jlong) new DetectorAgregator(mainDetector, trackingDetector);
        if (faceSize > 0) {
            mainDetector->setMinObjectSize(Size(faceSize, faceSize));
            //trackingDetector->setMinObjectSize(Size(faceSize, faceSize));
        }
    }
    catch (const cv::Exception &e) {
        LOGD("nativeCreateObject caught cv::Exception: %s", e.what());
        jclass je = jenv->FindClass("com/ooo/ttt/opencvlib/exception/CvException");
        if (!je)
            je = jenv->FindClass("java/lang/Exception");
        jenv->ThrowNew(je, e.what());
    }
    catch (...) {
        LOGD("nativeCreateObject caught unknown exception");
        jclass je = jenv->FindClass("java/lang/Exception");
        jenv->ThrowNew(je, "Unknown exception in JNI code of DetectionBasedTracker.nativeCreateObject()");
        return 0;
    }

    LOGD("face exit");
    return result;
}





extern "C"
JNIEXPORT void JNICALL
Java_com_ooo_ttt_opencvlib_FaceDector_nativeStart(JNIEnv *env, jclass clazz, jlong detector) {
    LOGD("face_nativeStart");

    try {
        ((DetectorAgregator *) detector)->tracker->run();
    }
    catch (const cv::Exception &e) {
        LOGD("nativeStart caught cv::Exception: %s", e.what());
        jclass je = env->FindClass("com/ooo/ttt/opencvlib/exception/CvException");
        if (!je)
            je = env->FindClass("java/lang/Exception");
        env->ThrowNew(je, e.what());
    }
    catch (...) {
        LOGD("nativeStart caught unknown exception");
        jclass je = env->FindClass("java/lang/Exception");
        env->ThrowNew(je, "Unknown exception in JNI code of DetectionBasedTracker.nativeStart()");
    }
    LOGD("face_nativeStart exit");
}

extern "C"
JNIEXPORT void JNICALL
Java_com_ooo_ttt_opencvlib_FaceDector_nativeStop(JNIEnv *env, jclass clazz, jlong detector) {
    LOGD("face_nativeStop");

    try {
        ((DetectorAgregator *) detector)->tracker->stop();
    }
    catch (const cv::Exception &e) {
        LOGD("nativeStop caught cv::Exception: %s", e.what());
        jclass je = env->FindClass("com/ooo/ttt/opencvlib/exception/CvException");
        if (!je)
            je = env->FindClass("java/lang/Exception");
        env->ThrowNew(je, e.what());
    }
    catch (...) {
        LOGD("nativeStop caught unknown exception");
        jclass je = env->FindClass("java/lang/Exception");
        env->ThrowNew(je, "Unknown exception in JNI code of DetectionBasedTracker.nativeStop()");
    }
    LOGD("face_nativeStop exit");
}


int camearDegree = 0;

extern "C"
JNIEXPORT void JNICALL
Java_com_ooo_ttt_opencvlib_FaceDector_setNativeDegree(JNIEnv *env, jclass clazz, jint degree) {
    camearDegree = degree;
}


int nativeCameraId;

extern "C"
JNIEXPORT void JNICALL
Java_com_ooo_ttt_opencvlib_FaceDector_setNativeCameraId(JNIEnv *env, jclass clazz, jint camera_id) {
    nativeCameraId = camera_id;
}

// todo 当我把手机朝着右边横放的时候还是识别出人脸了   LOGE("tag_face_size  %d", faces.size()); 这个打印出了数字
// 下一步，让它竖着放的时候也能识别出人脸
extern "C"
JNIEXPORT jobjectArray  JNICALL
Java_com_ooo_ttt_opencvlib_FaceDector_nativeDetect(JNIEnv *env, jclass clazz,
                                                   jlong detector,
                                                   jbyteArray inputImage_,
                                                   jint width,
                                                   jint height) {

    if (detector == 0) {
        return nullptr;
    }


    jbyte *inputImage = env->GetByteArrayElements(inputImage_, 0);//输入的YUV数据

    Mat image(height + height / 2, width, CV_8UC1, inputImage); // 摄像头数据data 转成 OpenCv的 Mat




    cvtColor(image, image, CV_YUV2BGR_NV21); // YUV420SP转BGR


//    LOGE("tag_camera_degree  %d", camearDegree);

    if (camearDegree == 90) {
        // 旋转90度, 竖屏的时候加上这行代码
        rotate(image, image, ROTATE_90_CLOCKWISE);


    } else if (camearDegree == 180) {
        rotate(image, image, ROTATE_180);
    } else if (camearDegree == 270) {
        rotate(image, image, ROTATE_90_COUNTERCLOCKWISE);
    }

    if (nativeCameraId == 1) {
        flip(image, image, 1); // y 轴 翻转（镜像操作）
    }

    cvtColor(image, image, COLOR_RGBA2GRAY); // BGRA  转  灰度图


    equalizeHist(image, image); // 均衡化处理（直方图均衡化，增强对比效果）


    vector<Rect> faces;

    ((DetectorAgregator *) detector)->tracker->process(image);
    ((DetectorAgregator *) detector)->tracker->getObjects(faces);


    LOGE("tag_face_size  %d", faces.size());
    int faceSize = faces.size();
    if (faceSize) {

        jclass clazz = env->FindClass("com/ooo/ttt/opencvlib/NativeFace");
        jmethodID construct = env->GetMethodID(clazz, "<init>", "(IIII)V");

        //给java 对象创建的数组
        jobjectArray facesForJava = env->NewObjectArray(faceSize, clazz, NULL);

        int index = 0;

        for (index = 0; index < faceSize; index++) {
            // x ， y 人脸左上角的坐标
            //width ,height 人脸方块区域的宽高
            Rect it = faces[index];
            int x = it.x;
            int y = it.y;
            int wth = it.width;
            int hei = it.height;
            jobject ob_tmp = env->NewObject(clazz, construct, x, y, wth, hei);
            env->SetObjectArrayElement(facesForJava, index, ob_tmp);
            env->DeleteLocalRef(ob_tmp);
        }


        return facesForJava;
    }


    env->ReleaseByteArrayElements(inputImage_, inputImage, 0);

    image.release();
    return nullptr;
}


//
//extern "C" JNIEXPORT jlong JNICALL nativeCreateObject(JNIEnv *jenv, jclass, jstring jFileName, jint faceSize) {
//    LOGD("face enter");
//    const char *jnamestr = jenv->GetStringUTFChars(jFileName, NULL);
//    string stdFileName(jnamestr);
//    jlong result = 0;
//
//    try {
//        cv::Ptr<CascadeDetectorAdapter> mainDetector = makePtr<CascadeDetectorAdapter>(
//                makePtr<CascadeClassifier>(stdFileName));
//        cv::Ptr<CascadeDetectorAdapter> trackingDetector = makePtr<CascadeDetectorAdapter>(
//                makePtr<CascadeClassifier>(stdFileName));
//        result = (jlong) new DetectorAgregator(mainDetector, trackingDetector);
//        if (faceSize > 0) {
//            mainDetector->setMinObjectSize(Size(faceSize, faceSize));
//            //trackingDetector->setMinObjectSize(Size(faceSize, faceSize));
//        }
//    }
//    catch (const cv::Exception &e) {
//        LOGD("nativeCreateObject caught cv::Exception: %s", e.what());
//        jclass je = jenv->FindClass("com/ooo/ttt/opencvlib/exception/CvException");
//        if (!je)
//            je = jenv->FindClass("java/lang/Exception");
//        jenv->ThrowNew(je, e.what());
//    }
//    catch (...) {
//        LOGD("nativeCreateObject caught unknown exception");
//        jclass je = jenv->FindClass("java/lang/Exception");
//        jenv->ThrowNew(je, "Unknown exception in JNI code of DetectionBasedTracker.nativeCreateObject()");
//        return 0;
//    }
//
//    LOGD("face exit");
//    return result;
//
//}
//
//
//extern "C" JNIEXPORT void JNICALL nativeStart(JNIEnv *jenv, jclass, jlong thiz) {
//    LOGD("face_nativeStart");
//
//    try {
//        ((DetectorAgregator *) thiz)->tracker->run();
//    }
//    catch (const cv::Exception &e) {
//        LOGD("nativeStart caught cv::Exception: %s", e.what());
//        jclass je = jenv->FindClass("com/ooo/ttt/opencvlib/exception/CvException");
//        if (!je)
//            je = jenv->FindClass("java/lang/Exception");
//        jenv->ThrowNew(je, e.what());
//    }
//    catch (...) {
//        LOGD("nativeStart caught unknown exception");
//        jclass je = jenv->FindClass("java/lang/Exception");
//        jenv->ThrowNew(je, "Unknown exception in JNI code of DetectionBasedTracker.nativeStart()");
//    }
//    LOGD("Java_org_opencv_samples_facedetect_DetectionBasedTracker_nativeStart exit");
//}
//
//
//
//extern "C" JNIEXPORT void JNICALL nativeStop(JNIEnv *jenv, jclass, jlong thiz) {
//    LOGD("face_nativeStop");
//
//    try {
//        ((DetectorAgregator *) thiz)->tracker->stop();
//    }
//    catch (const cv::Exception &e) {
//        LOGD("nativeStop caught cv::Exception: %s", e.what());
//        jclass je = jenv->FindClass("com/ooo/ttt/opencvlib/exception/CvException");
//        if (!je)
//            je = jenv->FindClass("java/lang/Exception");
//        jenv->ThrowNew(je, e.what());
//    }
//    catch (...) {
//        LOGD("nativeStop caught unknown exception");
//        jclass je = jenv->FindClass("java/lang/Exception");
//        jenv->ThrowNew(je, "Unknown exception in JNI code of DetectionBasedTracker.nativeStop()");
//    }
//    LOGD("face_nativeStop exit");
//}
//
//
//
//extern "C" JNIEXPORT void JNICALL nativeSetFaceSize(JNIEnv *jenv, jclass, jlong thiz, jint faceSize) {
//    LOGD("face_nativeSetFaceSize -- BEGIN");
//
//    try {
//        if (faceSize > 0) {
//            ((DetectorAgregator *) thiz)->mainDetector->setMinObjectSize(Size(faceSize, faceSize));
//            //((DetectorAgregator*)thiz)->trackingDetector->setMinObjectSize(Size(faceSize, faceSize));
//        }
//    }
//    catch (const cv::Exception &e) {
//        LOGD("nativeStop caught cv::Exception: %s", e.what());
//        jclass je = jenv->FindClass("com/ooo/ttt/opencvlib/exception/CvException");
//        if (!je)
//            je = jenv->FindClass("java/lang/Exception");
//        jenv->ThrowNew(je, e.what());
//    }
//    catch (...) {
//        LOGD("nativeSetFaceSize caught unknown exception");
//        jclass je = jenv->FindClass("java/lang/Exception");
//        jenv->ThrowNew(je, "Unknown exception in JNI code of DetectionBasedTracker.nativeSetFaceSize()");
//    }
//    LOGD("Java_org_opencv_samples_facedetect_DetectionBasedTracker_nativeSetFaceSize -- END");
//}
//
//
//
//extern "C" JNIEXPORT void JNICALL nativeDetect(JNIEnv *jenv, jclass, jlong thiz, jlong imageGray, jlong faces) {
//    LOGD("face_nativeStart");
//
//    try {
//        ((DetectorAgregator *) thiz)->tracker->run();
//    }
//    catch (const cv::Exception &e) {
//        LOGD("nativeStart caught cv::Exception: %s", e.what());
//        jclass je = jenv->FindClass("com/ooo/ttt/opencvlib/exception/CvException");
//        if (!je)
//            je = jenv->FindClass("java/lang/Exception");
//        jenv->ThrowNew(je, e.what());
//    }
//    catch (...) {
//        LOGD("nativeStart caught unknown exception");
//        jclass je = jenv->FindClass("java/lang/Exception");
//        jenv->ThrowNew(je, "Unknown exception in JNI code of DetectionBasedTracker.nativeStart()");
//    }
//    LOGD("Java_org_opencv_samples_facedetect_DetectionBasedTracker_nativeStart exit");
//}
//
//
//
//
//extern "C" JNIEXPORT void JNICALL nativeDestroyObject(JNIEnv *jenv, jclass, jlong thiz) {
//    LOGD("Java_org_opencv_samples_facedetect_DetectionBasedTracker_nativeDestroyObject");
//
//    try {
//        if (thiz != 0) {
//            ((DetectorAgregator *) thiz)->tracker->stop();
//            delete (DetectorAgregator *) thiz;
//        }
//    }
//    catch (const cv::Exception &e) {
//        LOGD("nativeestroyObject caught cv::Exception: %s", e.what());
//        jclass je = jenv->FindClass("com/ooo/ttt/opencvlib/exception/CvException");
//        if (!je)
//            je = jenv->FindClass("java/lang/Exception");
//        jenv->ThrowNew(je, e.what());
//    }
//    catch (...) {
//        LOGD("nativeDestroyObject caught unknown exception");
//        jclass je = jenv->FindClass("java/lang/Exception");
//        jenv->ThrowNew(je, "Unknown exception in JNI code of DetectionBasedTracker.nativeDestroyObject()");
//    }
//    LOGD("Java_org_opencv_samples_facedetect_DetectionBasedTracker_nativeDestroyObject exit");
//}
//
//
//static JNINativeMethod g_RenderMethods[] = {
//        {"nativeCreateObject",  "(Ljava/lang/String;I)V", (void *) (nativeCreateObject)},
//        {"nativeStart",         "(L)V",                   (void *) (nativeStart)},
//        {"nativeStop",          "(L)V",                   (void *) (nativeStop)},
//
//        {"nativeSetFaceSize",   "(LI)V",                  (void *) (nativeSetFaceSize)},
//        {"nativeDetect",        "(LI)V",                  (void *) (nativeDetect)},
//        {"nativeDestroyObject", "()V",                    (void *) (nativeDestroyObject)},
//
//};
//
//
//static int RegisterNativeMethods(JNIEnv *env, const char *className, JNINativeMethod *methods, int methodNum) {
//    LOGI("RegisterNativeMethods");
//    jclass clazz = env->FindClass(className);
//    if (clazz == NULL) {
//        LOGI("RegisterNativeMethods fail. clazz == NULL");
//        return JNI_FALSE;
//    }
//    if (env->RegisterNatives(clazz, methods, methodNum) < 0) {
//        LOGI("RegisterNativeMethods fail");
//        return JNI_FALSE;
//    }
//    return JNI_TRUE;
//
//}
//
//static void UnregisterNativeMethods(JNIEnv *env, const char *className) {
//    LOGI("UnregisterNativeMethods");
//    jclass clazz = env->FindClass(className);
//    if (clazz == NULL) {
//        LOGI("UnregisterNativeMethods fail. clazz == NULL");
//        return;
//    }
//    if (env != NULL) {
//        env->UnregisterNatives(clazz);
//    }
//}
//
//// call this func when loading lib
//extern "C" jint JNI_OnLoad(JavaVM *jvm, void *p) {
//    LOGI("===== JNI_OnLoad =====");
//    jint jniRet = JNI_ERR;
//    JNIEnv *env = NULL;
//    if (jvm->GetEnv((void **) (&env), JNI_VERSION_1_6) != JNI_OK) {
//        return jniRet;
//    }
//
//    jint regRet = RegisterNativeMethods(env, NATIVE_RENDER_CLASS_NAME, g_RenderMethods,
//                                        sizeof(g_RenderMethods) /
//                                        sizeof(g_RenderMethods[0]));
//    if (regRet != JNI_TRUE) {
//        return JNI_ERR;
//    }
//
//
//    return JNI_VERSION_1_6;
//}
//
//extern "C" void JNI_OnUnload(JavaVM *jvm, void *p) {
//    JNIEnv *env = NULL;
//    if (jvm->GetEnv((void **) (&env), JNI_VERSION_1_6) != JNI_OK) {
//        return;
//    }
//
//    UnregisterNativeMethods(env, NATIVE_RENDER_CLASS_NAME);
//}









